﻿using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;

namespace LAeMail.Infrastructure.Controls
{
    public class HintTextBox : TextBox
    {
        #region Constructors

        public HintTextBox() : base()
        {
        }

        #endregion

        #region Properties

        // Using a DependencyProperty as the backing store for Note.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty NoteProperty = DependencyProperty.Register("Note", typeof(string), typeof(HintTextBox), new UIPropertyMetadata("", LabelPropertyChanged));

        // Using a DependencyProperty as the backing store for NoteStyle.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty NoteStyleProperty = DependencyProperty.Register("NoteStyle", typeof(Style), typeof(HintTextBox), new UIPropertyMetadata(null));

        private static readonly DependencyPropertyKey HasTextPropertyKey = DependencyProperty.RegisterReadOnly("HasText", typeof(bool), typeof(HintTextBox), new PropertyMetadata(false));

        public static readonly DependencyProperty HasTextProperty = HasTextPropertyKey.DependencyProperty;

        public string Note { get { return (string)GetValue(NoteProperty); } set { SetValue(NoteProperty, value); } }
        public Style NoteStyle { get { return (Style)GetValue(NoteStyleProperty); } set { SetValue(NoteStyleProperty, value); } }
        public bool HasText { get { return (bool)GetValue(HasTextProperty); } private set { SetValue(HasTextPropertyKey, value); } }

        #endregion

        #region Callbacks

        private static void LabelPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var infoTextBox = d as HintTextBox;

            if (infoTextBox != null)
                infoTextBox.UpdateAdorner(infoTextBox);
            var isVisiblePropertyDescriptor = DependencyPropertyDescriptor.FromProperty(IsVisibleProperty, typeof(HintTextBox));
            isVisiblePropertyDescriptor.AddValueChanged(d, IsVisibleChanged);
        }

        #endregion

        private AdornerLabel myAdornerLabel;
        private AdornerLayer myAdornerLayer;

        private new static void IsVisibleChanged(object sender, EventArgs e)
        {
            var infoTextBox = sender as HintTextBox;
            if (infoTextBox == null)
                return;

            infoTextBox.UpdateAdorner(infoTextBox, !infoTextBox.IsVisible);
        }

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            myAdornerLayer = AdornerLayer.GetAdornerLayer(this);
            myAdornerLabel = new AdornerLabel(this, Note, NoteStyle);
            UpdateAdorner(this);

            var focusProp = DependencyPropertyDescriptor.FromProperty(IsFocusedProperty, typeof(FrameworkElement));
            if (focusProp != null)
                focusProp.AddValueChanged(this, delegate { UpdateAdorner(this); });

            var containsTextProp = DependencyPropertyDescriptor.FromProperty(HasTextProperty, typeof(HintTextBox));
            if (containsTextProp != null)
                containsTextProp.AddValueChanged(this, delegate { UpdateAdorner(this); });
        }

        protected override void OnTextChanged(TextChangedEventArgs e)
        {
            HasText = Text != "";

            base.OnTextChanged(e);
        }

        protected override void OnDragEnter(DragEventArgs e)
        {
            myAdornerLayer.RemoveAdorners<AdornerLabel>(this);

            base.OnDragEnter(e);
        }

        protected override void OnDragLeave(DragEventArgs e)
        {
            UpdateAdorner(this);

            base.OnDragLeave(e);
        }

        private void UpdateAdorner(FrameworkElement elem, bool hide = false)
        {
            if (elem == null || myAdornerLayer == null)
                return;

            myAdornerLabel = new AdornerLabel(this, Note, NoteStyle);
            myAdornerLayer.RemoveAdorners<AdornerLabel>(elem);

            if (!((HintTextBox) elem).HasText && !elem.IsFocused && !hide)
                myAdornerLayer.Add(myAdornerLabel);
        }
    }

    // Adorners must subclass the abstract base class Adorner.
    public class AdornerLabel : Adorner
    {
        private readonly TextBlock textBlock;

        // Be sure to call the base class constructor.
        public AdornerLabel(UIElement adornedElement, string label, Style labelStyle)
            : base(adornedElement)
        {
            textBlock = new TextBlock { Style = labelStyle, Text = label };
        }

        protected override int VisualChildrenCount { get { return 1; } }

        //make sure that the layout system knows of the element
        protected override Size MeasureOverride(Size constraint)
        {
            textBlock.Measure(constraint);
            return constraint;
        }

        //make sure that the layout system knows of the element
        protected override Size ArrangeOverride(Size finalSize)
        {
            textBlock.Arrange(new Rect(finalSize));
            return finalSize;
        }

        //return the visual that we want to display
        protected override Visual GetVisualChild(int index)
        {
            return textBlock;
        }

        //return the count of the visuals
    }

    public static class AdornerExtensions
    {
        public static void RemoveAdorners<T>(this AdornerLayer adr, UIElement elem)
        {
            var adorners = adr.GetAdorners(elem);

            if (adorners == null)
                return;

            for (var i = adorners.Length - 1; i >= 0; i--)
            {
                if (adorners[i] is T)
                    adr.Remove(adorners[i]);
            }
        }

        public static bool Contains<T>(this AdornerLayer adr, UIElement elem)
        {
            if (adr == null)
                return false;
            var adorners = adr.GetAdorners(elem);

            if (adorners == null)
                return false;

            for (var i = adorners.Length - 1; i >= 0; i--)
            {
                if (adorners[i] is T)
                    return true;
            }
            return false;
        }

        public static void RemoveAll(this AdornerLayer adr, UIElement elem)
        {
            try
            {
                var adorners = adr.GetAdorners(elem);

                if (adorners == null)
                    return;

                foreach (var toRemove in adorners)
                    adr.Remove(toRemove);
            }
            catch
            {
            }
        }

        public static void RemoveAllRecursive(this AdornerLayer adr, UIElement element)
        {
            try
            {
                Action<UIElement> recurse = null;
                recurse = ((Action<UIElement>)delegate(UIElement elem)
                {
                    adr.RemoveAll(elem);
                    if (elem is Panel)
                    {
                        foreach (UIElement e in ((Panel)elem).Children)
                            recurse(e);
                    }
                    else if (elem is Decorator)
                        recurse(((Decorator)elem).Child);
                    else if (elem is ContentControl)
                    {
                        if (((ContentControl)elem).Content is UIElement)
                            recurse(((ContentControl)elem).Content as UIElement);
                    }
                });

                recurse(element);
            }
            catch
            {
            }
        }
    }
}
